package abacab.queue;

import java.util.concurrent.atomic.AtomicInteger;

import abacab.actor.Actor;


/**
 * An actor queue handling priorities
 */
public class PriorityActorQueue extends ActorQueue {
	/** The factory for priority levels */
	private final PriorityLevelManager	factory;

	/** The priority levels indexes */
	private final LevelActorQueue[]			priorityLevels;

	/** The head level in the priority queue */
	private final AtomicInteger					headLevel;

	/**
	 * Create an instance
	 * 
	 * @param factory
	 *          the factory to use to generate the priority levels
	 */
	@SuppressWarnings("synthetic-access")
	public PriorityActorQueue(PriorityLevelManager factory) {
		this.factory = factory;
		this.priorityLevels = new LevelActorQueue[factory.getLevels()];
		for (int i = 0; i < this.priorityLevels.length; i++) {
			this.priorityLevels[i] = factory.getQueue(new PriorityQueueLevel(i));
		}
		this.headLevel = new AtomicInteger(this.priorityLevels.length);
	}

	@Override
	public boolean isEmpty() {
		int level = this.headLevel.get();
		while (level < this.priorityLevels.length) {
			if (!this.priorityLevels[level].isEmpty()) {
				return false;
			}
			level++;
		}
		return true;
	}

	@Override
	public int size() {
		int size = 0;
		for (int i = 0; i < this.priorityLevels.length; i++) {
			size += this.priorityLevels[i].size();
		}
		return size;
	}

	@Override
	public <M> void offer(Actor<M> actor) {
		int level = this.factory.getPriorityLevel(actor);
		LevelActorQueue queue = this.priorityLevels[level];
		queue.offer(actor);
	}

	@Override
	public <M> Actor<M> poll() {
		int level = this.headLevel.get();
		if (level < this.priorityLevels.length) {
			Actor<M> actor = this.priorityLevels[level].poll();
			while (actor == null && ++level < this.priorityLevels.length) {
				actor = this.priorityLevels[level].poll();
			}
			return actor;
		}
		return null;
	}

	//
	//
	//
	//
	//
	//
	//
	//
	//
	//
	// ************************************************************************
	// ************************************************************************
	// ASSOCIATED CLASSES
	// ************************************************************************
	// ************************************************************************

	/**
	 * A manager to build the queue for each priority level and handle affectations
	 */
	public interface PriorityLevelManager {
		/**
		 * @return the number of priority levels
		 */
		int getLevels();

		/**
		 * @param actor
		 *          the actor
		 * @return the priority level for the actor
		 */
		<M> int getPriorityLevel(Actor<M> actor);

		/**
		 * Get an instance of a queue for a particular priority level.<br />
		 * The queue must be able to handle concurrent access.
		 * 
		 * @param level
		 *          the priority level interface
		 * @return the queue instance
		 */
		LevelActorQueue getQueue(PriorityQueueLevel level);
	}

	/**
	 * The interface with the inner queues
	 */
	@SuppressWarnings({ "synthetic-access", "unqualified-field-access" })
	public final class PriorityQueueLevel {
		/** The priority level */
		private final int	level;

		/**
		 * Create an instance
		 * 
		 * @param level
		 *          the priority level
		 */
		private PriorityQueueLevel(int level) {
			this.level = level;
		}

		/**
		 * @return the priority level
		 */
		public int getLevel() {
			return this.level;
		}

		/**
		 * Indicates that the first actor was inserted in the queue
		 */
		protected final void firstActor() {
			int oldLevel;
			do {
				oldLevel = headLevel.get();
				if (this.level >= oldLevel) {
					break;
				}
			} while (!headLevel.compareAndSet(oldLevel, this.level));
		}

		/**
		 * Indicates that the last actor was removed from the queue
		 */
		protected final void lastActor() {
			headLevel.compareAndSet(this.level, this.level + 1);
		}
	}

	/**
	 * The base implementation for queues to use for each level
	 */
	public static abstract class LevelActorQueue extends ActorQueue {
		/** The level in the priority queue */
		private final PriorityQueueLevel	level;

		/**
		 * Create an instance
		 * 
		 * @param level
		 *          the priority level interface
		 */
		public LevelActorQueue(PriorityQueueLevel level) {
			this.level = level;
		}

		/**
		 * Add an actor to the queue.<br/>
		 * An implementation MUST call {@link #firstActor()} if the queue was empty prior to inserting.
		 * 
		 * @param actor
		 *          the actor
		 */
		@Override
		public abstract <M> void offer(Actor<M> actor);

		/**
		 * Get the first suitable actor in the queue.<br />
		 * The implementation MUST return the first actor that succeeds in calling {@link #tryLockActor(Actor)}. Other actors should be kept in the queue.<br/>
		 * The implementation MUST also call
		 * 
		 * @return the first actor in the queue
		 */
		@Override
		public abstract <M> Actor<M> poll();

		/**
		 * Indicates that the first actor was inserted in the queue
		 */
		protected final void firstActor() {
			this.level.firstActor();
		}

		/**
		 * Indicates that the last actor was removed from the queue
		 */
		protected final void lastActor() {
			this.level.lastActor();
		}
	}
}